package com.firemoss.magicbus.rpc
{
	import com.firemoss.magicbus.dto.AssemblerInvoker;
	import com.firemoss.magicbus.dto.DTOAssembler;
	
	import mx.rpc.AsyncToken;
	import mx.rpc.Responder;
	import mx.rpc.events.FaultEvent;
	import mx.rpc.events.ResultEvent;
	import mx.rpc.remoting.RemoteObject;
	import mx.rpc.remoting.mxml.Operation;

	public class ServiceMethod extends Operation
	{
		
		include "./filterProperties.inc";
		include "./mediationProperties.inc";

		/**
		 * If defined, the arguments sent to this operation will be placed on result / fault events with 
		 * the names from this array.  It's probably the list of arguments (in the same order) that your
		 * service-side method expects. 
		 */
		public var argumentProperties : Array
		
		public var rpcService : Service
		
		public var assembler : DTOAssembler
		public var disassembler : DTOAssembler
		
		/**
		 * Should event names in the format { remoteObject.id }.{ operationName }.{ "sent" | "result" | "fault" } 
		 * automatically be dispatched?
		 */
		public var dispatchLifecycleEvents : Boolean = true
		
		private var _assemblerInvoker : AssemblerInvoker = new AssemblerInvoker()
		private function get assemblerInvoker() : AssemblerInvoker
		{
			_assemblerInvoker.assembler = assembler
			_assemblerInvoker.disassembler = disassembler
			
			return _assemblerInvoker
		}
		
		public function ServiceMethod(remoteObject:RemoteObject=null, name:String=null)
		{
			super(remoteObject, name);
			
			if ( remoteObject is Service )
			{
				rpcService = remoteObject as Service
			}
		}
		
		override public function send( ...args : Array ) : AsyncToken
		{
			var originalArguments : Array = new Array()
			
			for each ( var arg : * in args )
			{
				originalArguments.push( arg )
			}
			
			// "args" is misleading:  AbstractService does:
			// send.apply(null, args);
			// therefore, our actual method args are ...args[0]
			
			if ( disassembler )
			{
				assemblerInvoker.before( args )
			}
			
			executeBeforeChain( args )
			
			sendBeforeNotifications( args )

			var token : AsyncToken = super.send.apply( null, args )
						
			token.originalArguments = originalArguments
			token.filteredArguments = args
			
			addResponders( token )
			
			return token
		} 
		
		protected function sendBeforeNotifications( args : Array ) : void
		{
			if ( rpcService.sendEvent )
			{
				rpcService.dispatchRpcEvent( rpcService.sendEvent, args, "parameters", null, rpcService.sendEventClass ? rpcService.sendEventClass : rpcService.eventClass ) 
			}

			if ( sendEvent )
			{
				rpcService.dispatchRpcEvent( sendEvent, args, "parameters", null, sendEventClass ? sendEventClass : eventClass ) 
			}
			
			if ( dispatchLifecycleEvents )
			{
				rpcService.dispatchRpcEvent( rpcService.mxmlId + "." + name + ".sent", args, "parameters")
			}
		}
		
		protected function sendResultNotifications( event : ResultEvent, explicitResultValue : Object  ) : void
		{
			if ( !explicitResultValue )
			{
				explicitResultValue = event.result
			}
			
			var props : Object = createAdditionalPropertiesMap( event.token )
			
			if ( resultEvent && resultEventProperty )
			{
				rpcService.dispatchRpcEvent( resultEvent, explicitResultValue, resultEventProperty, props, resultEventClass ? resultEventClass : eventClass ) 
			}

			if ( rpcService.resultEvent )
			{
				rpcService.dispatchRpcEvent( rpcService.resultEvent, explicitResultValue, resultEventProperty, props, rpcService.resultEventClass ? rpcService.resultEventClass : rpcService.eventClass ) 
			}

			if ( dispatchLifecycleEvents )
			{
				rpcService.dispatchRpcEvent( rpcService.mxmlId + "." + name + ".result", explicitResultValue, resultEventProperty, props ) 
			}
		}
		
		protected function sendFaultNotifications( event : FaultEvent ) : void
		{
			var props : Object = createAdditionalPropertiesMap( event.token )

			if ( faultEvent )
			{
				rpcService.dispatchRpcEvent( faultEvent, event.fault, "fault", props, faultEventClass ? faultEventClass : eventClass ) 
			}

			if ( rpcService.faultEvent )
			{
				rpcService.dispatchRpcEvent( rpcService.faultEvent, event.fault, "fault", props, rpcService.faultEventClass ? rpcService.faultEventClass : rpcService.eventClass ) 
			}

			if ( dispatchLifecycleEvents )
			{
				rpcService.dispatchRpcEvent( rpcService.mxmlId + "." + name + ".fault", event.fault, "fault", props ) 
			}
		}
		
		protected function executeBeforeChain( args : Array ) : Array
		{
			if ( rpcService && rpcService.beforeChain )
			{
				return rpcService.beforeChain.execute( args )
			}
			
			if ( beforeChain )
			{
				return beforeChain.execute( args )
			}
			
			return null
		}
		
		protected function addResponders( token : AsyncToken ) : void
		{
			token.addResponder( new Responder( onResult, onFault ) )
		}		
		
		private function onResult( event : ResultEvent ) : void
		{
			var result : Object
			
			if ( assembler )
			{
				result = assemblerInvoker.result( event.result, event )
			}
			
			if ( resultChain )
			{
				result = resultChain.execute( event, result )
			}

			if ( rpcService && rpcService.resultChain )
			{
				result = rpcService.resultChain.execute( event, result )
			}
			
			if ( resultEvent )
			{
				sendResultNotifications( event, result )
			}
		}
		
		private function onFault( event : FaultEvent ) : void
		{
			if ( faultChain )
			{
				faultChain.execute( event )
			}

			if ( rpcService && rpcService.faultChain )
			{
				rpcService.faultChain.execute( event )
			}

			if ( faultEvent )
			{
				sendFaultNotifications( event )
			}
		}
		
		private function createAdditionalPropertiesMap( token : AsyncToken ) : Object
		{
			var props : Object = {}
			
			if ( argumentProperties )
			{
				// We're going to assume people can count.
				for ( var i : int = 0; i < argumentProperties.length; i++ )
				{
					if ( argumentProperties[ i ] != resultEventProperty )
					{
						props[ argumentProperties[ i ] ] = token.originalArguments[ i ] 
					}
				}
			}
			
			if ( tokenProperties )
			{
				for each ( var key : String in tokenProperties.split(",") )
				{
					props[ key ] = token.hasOwnProperty( key ) ? token[ key ] : null
				}
			}
			
			return props
		}
		
	}
}